/*
 * 
 * Copyright (C) 2010-2012 Wilfried Elmenreich, Anita Sobe 
 * This file is part of VideoNetwork.
 * 
 * VideoNetwork is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 
 * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. 
 */
package main;

import java.util.ArrayList;
import java.util.Random;

import proxy.Content;
import proxy.VideoUnit;
import simulation.Simulator;
import simulation.StatKeeper;
import util.OutputGenerator;

public class Main {

	static ArrayList<StatKeeper> notableStats = new ArrayList<StatKeeper>();
	private OutputGenerator output;

	StatKeeper requestsDone;
	StatKeeper interUnitDelay;
	StatKeeper interUnitJitter;
	StatKeeper migrationMoves;
	StatKeeper migrationCopies;
	StatKeeper avDelay;
	StatKeeper thruput;
	StatKeeper viewingFulfilment;
	StatKeeper averageDiskUsage;
	StatKeeper hitrate;
	StatKeeper numReplicas;
	StatKeeper avgUnitKeeptime;
	StatKeeper avgNumHops;
	StatKeeper avgOccupationTime;
	StatKeeper activeReplicas;
	StatKeeper deadlineCount;
	StatKeeper deadlineStat;
	StatKeeper requestsPartiallyFulfilled;
	StatKeeper requestsFailed;
	StatKeeper openRequests;

	StatKeeper unitUtilization;// units currently in use (in presentation)
	StatKeeper cleanupRemoved;
	StatKeeper cleanupMovedPhero;
	StatKeeper cleanupMovedSpace;
	StatKeeper cleanupFailed;

	StatKeeper messageNumber;

	static ArrayList<StatKeeper> interRequestDelay = new ArrayList<StatKeeper>();
	static final InformationDisseminationConfig simConfig = new InformationDisseminationConfig();

	// static final UnitSizeConfig simConfig = new UnitSizeConfig();

	// static final RefSearchDirectDownloadConfig simConfig = new
	// RefSearchDirectDownloadConfig();
	// static final ReplicationConfigRandom simConfig = new
	// ReplicationConfigRandom();
	// static final ReplicationConfigRandomSeq simConfig = new
	// ReplicationConfigRandomSeq();
	// static final ReplicationConfigRandomChurn simConfig = new
	// ReplicationConfigRandomChurn();
	// static final ReplicationConfigScaleFree simConfig = new
	// ReplicationConfigScaleFree();

	volatile static int defaultSeed = 1234;
	static final Random rand = new Random(defaultSeed);
	int seed;

	// name in
	// order to
	// have this
	// column
	// sorted
	// first

	public void singleRun() {

		// initialize statistics
		synchronized (notableStats) {
			initStats();
		}

		Simulator simulator = simConfig.initSimulator(seed);
		output = new OutputGenerator(simulator.getContentTypes(),
				simulator.getNodes());

		// here you can generate gephi and pajec network visualization of the
		// generated graphs
		// generateNetworkGraphs();

		// startup simulator

		int length = simulator.delayPerUnitwithinRequest.size();
		for (int i = 0; i < length; i++) {
			interRequestDelay.add(createStatKeeper("n" + i));
		}

		// create tastes of clients
		simulator.updateFavorites();

		// bootstrap client requests
		simulator.processViewings();

		// go to work
		performSimulation(simulator);

		// done - do clean-up
		simulator.close();

	}

	private void performSimulation(Simulator simulator) {

		// choose hormone to follow
		Content c = simulator.getVideoUnitDirectory().get(0).getContent();

		// do simulation step by step
		for (int i = 0; i < simConfig.simTime; i++) {

			// decide whether a node should be added or removed
			if (simConfig.nodeFailure
					&& rand.nextDouble() <= simConfig.churnProbability)
				simulateNodeFailure(simulator);

			// do statistics
			if (i % simConfig.reportingGranularity == 0) {

				if (simConfig.withHormoneVisualization) {
					output.serverNodes2GephiWithspecificHormone("out" + i
							+ ".gexf", c);
				}

				// count statistics - have to be initialized first!!!

				// migrationMoves.add(simulator.migrationMoveCounter);
				// simulator.migrationMoveCounter = 0;

				// migrationCopies.add(simulator.migrationCopyCounter);
				// simulator.migrationCopyCounter = 0;

				// deadlineCount.add(simulator.deadlineMissedCount
				// / (double) simulator.deadlineSetCount);
				//
				// simulator.deadlineSetCount = simulator.deadlineSetCount
				// - simulator.deadlineMissedCount
				// - simulator.deadlineNotMissedCount;
				// simulator.deadlineNotMissedCount = 0;
				// simulator.deadlineMissedCount = 0;

				messageNumber.add(simulator.messageCount);

				// activeReplicas.add(simulator.movedBecauseOfPrefetching);
				// simulator.movedBecauseOfPrefetching = 0;

				avDelay.add(simulator.delayStat.mean());
				simulator.delayStat.clear();

				// interUnitDelay.add(simulator.interCompositionDelay.mean());
				// simulator.interCompositionDelay.clear();

				// interUnitJitter.add(simulator.interCompositionJitter.mean());
				// simulator.interCompositionJitter.clear();

				// for (int k = 0; k < interRequestDelay.size(); k++) {
				// interRequestDelay.get(k).add(
				// simulator.delayPerUnitwithinRequest.get(k).mean());
				// simulator.delayPerUnitwithinRequest.get(k).clear();
				// }

				// avgUnitKeeptime.add(simulator.unitKeepStat.mean());
				// simulator.unitKeepStat.clear();
				//
				// avgNumHops.add(simulator.numHopsStat.mean());
				// simulator.numHopsStat.clear();
				//
				// avgOccupationTime.add(simulator.occupation.mean());
				// simulator.occupation.clear();

				// double hr = 0.0;
				// int fulfilled = simulator.hitStat.n;
				// if (fulfilled != 0) {
				// hr = simulator.hitStat.countHit0 / (double) fulfilled;
				// hitrate.add(hr);
				// }

				// averageDiskUsage.add(simulator.serverDiskUsage().mean());

				// StatKeeper replicaStat = new StatKeeper();
				// for (int j = 0; j < simulator.getVideoUnitDirectory().size();
				// j++) {
				// replicaStat.add(simulator.unitCount(simulator
				// .getVideoUnitDirectory().get(j).getId()));
				// }
				// numReplicas.add(replicaStat.mean());

				// for (int id : simulator.unitUtilizationCount.keySet()) {
				// unitUtilization.add(simulator.unitUtilizationCount.get(id));
				// }
				// simulator.unitUtilizationCount.clear();
				// simulator.unitsCurrentlyInPresentation.clear();

				// cleanupFailed.add(simulator.countCleanupFailed);
				// simulator.countCleanupFailed = 0;
				//
				// cleanupRemoved.add(simulator.countRemoved);
				// simulator.countRemoved = 0;

				// cleanupMovedPhero.add(simulator.countMovedPhero);
				// simulator.countMovedPhero = 0;
				//
				// cleanupMovedSpace.add(simulator.countMovedSpace);
				// simulator.countMovedSpace = 0;

				// deadlineStat.add(simulator.deadlineStat.mean());
				// simulator.deadlineStat.clear();

				requestsDone.add(simulator.requestFulfilledCounter
						/ (double) simulator.requestSubmittedCounter);
				// requestsPartiallyFulfilled
				// .add(simulator.requestsPartiallyFulfilled
				// / (double) simulator.requestSubmittedCounter);
				// requestsFailed.add(simulator.requestsNotFulfilledAtall
				// / (double) simulator.requestSubmittedCounter);
				//
				// openRequests
				// .add((simulator.requestSubmittedCounter -
				// (simulator.requestFulfilledCounter
				// + simulator.requestsNotFulfilledAtall +
				// simulator.requestsPartiallyFulfilled))
				// / (double) simulator.requestSubmittedCounter);

				// if (simulator.getScenario() != ScenarioType.DISSEMINATION) {
				//
				// simulator.requestSubmittedCounter -=
				// (simulator.requestFulfilledCounter
				// + simulator.requestsPartiallyFulfilled +
				// simulator.requestsNotFulfilledAtall);
				//
				// simulator.requestFulfilledCounter = 0;
				// simulator.requestsPartiallyFulfilled = 0;
				// simulator.requestsNotFulfilledAtall = 0;
				// }

			}
			simulator.advance(0.1);
		}
	}

	private void simulateNodeFailure(Simulator simulator) {
		// randomly delete or add node
		if (simulator.getRand().nextBoolean()) {
			int id = simulator.deleteServer();
			simulator.log("deleting server " + id);
		} else {
			int id = simulator.addNode();
			simulator.log("adding node " + id);
		}
	}

	void generateNetworkGraphs() {
		output.serverNodes2pajek("nodes" + this.seed + ".net");
		output.serverNodes2Gephi("nodes.gexf");
		output.serverNodes2CDNSim("network" + simConfig.nServers + ".txt");
	}

	/**
	 * Statistics that should be written to file in the end of the simulation
	 */
	private void initStats() {
		// Add statistics:

		requestsDone = createStatKeeper("zrequestsDone");
		addToNotableStats(requestsDone);

		// requestsPartiallyFulfilled =
		// createStatKeeper("zrequestsPartiallyDone");
		// addToNotableStats(requestsPartiallyFulfilled);
		//
		// requestsFailed = createStatKeeper("zrequestsFailed");
		// addToNotableStats(requestsFailed);
		//
		// openRequests = createStatKeeper("zopenRequests");
		// addToNotableStats(openRequests);

		// // 2) number of move actions
		// migrationMoves = createStatKeeper("migrationMoves");
		// addToNotableStats(migrationMoves);
		// // // 3) number of copy actions
		// migrationCopies = createStatKeeper("migrationCopies");
		// addToNotableStats(migrationCopies);
		// // 4) delay from request to fulfillment
		avDelay = createStatKeeper("avDelay");
		addToNotableStats(avDelay);

		// interUnitDelay = createStatKeeper("compDelay");
		// addToNotableStats(interUnitDelay);

		// interUnitJitter = createStatKeeper("compJitter");
		// addToNotableStats(interUnitJitter);
		// 5) throughput
		// thruput = createStatKeeper("unitRequestRate");
		// addToNotableStats(thruput);

		// averageDiskUsage = createStatKeeper("averageDiskUsage");
		// addToNotableStats(averageDiskUsage);

		// hitrate = createStatKeeper("hitrate");
		// addToNotableStats(hitrate);

		// deadlineCount = createStatKeeper("deadlineCount");
		// addToNotableStats(deadlineCount);
		// //
		// deadlineStat = createStatKeeper("deadlineStat");
		// addToNotableStats(deadlineStat);
		// // delayCum = createStatKeeper("delayCum");
		// addToNotableStats(delayCum);
		// counts replicas for all units at a given time period

		// averages over time-> writes to file
		// numReplicas = createStatKeeper("numReplicas");
		// addToNotableStats(numReplicas);

		// // how long a unit stays on a node, normalized per KB
		// avgUnitKeeptime = createStatKeeper("unitKeepTimeKB");
		// addToNotableStats(avgUnitKeeptime);

		// num hops travelled
		// avgNumHops = createStatKeeper("avgNumHopsTravelled");
		// addToNotableStats(avgNumHops);
		//
		// avgOccupationTime = createStatKeeper("avgLinkOccupation");
		// addToNotableStats(avgOccupationTime);

		// activeReplicas = createStatKeeper("activeReplicas");
		// addToNotableStats(activeReplicas);

		// unitUtilization = createStatKeeper("unitUtilization");
		// addToNotableStats(unitUtilization);
		//
		// cleanupRemoved = createStatKeeper("cleanupRemove");
		// addToNotableStats(cleanupRemoved);

		// cleanupMovedPhero = createStatKeeper("cleanupMovePhero");
		// addToNotableStats(cleanupMovedPhero);
		//
		// cleanupMovedSpace = createStatKeeper("cleanupMoveSpace");
		// addToNotableStats(cleanupMovedSpace);

		// cleanupFailed = createStatKeeper("cleanupFailed");
		// addToNotableStats(cleanupFailed);

		messageNumber = createStatKeeper("messageNummer");
		addToNotableStats(messageNumber);
	}

	private StatKeeper createStatKeeper(String name) {
		return new StatKeeper(true, name + "." + this.seed);
	}

	private void addToNotableStats(StatKeeper stat) {
		notableStats.add(stat);
	}


	public static void main(String[] args) {

		for (int i = 0; i < simConfig.numRuns; i++) {
			Main runProgram = new Main();
			runProgram.seed = rand.nextInt();
			runProgram.singleRun();
		}

		/*
		 * Threaded version
		 * 
		 * Thread[] ts = new Thread[simConfig.numRuns];
		 * 
		 * for (int i = 0; i < simConfig.numRuns; i++) {
		 * 
		 * ts[i] = new Thread() { public void run() { Main runProgram = new
		 * Main(); runProgram.seed = rand.nextInt(); runProgram.singleRun(); };
		 * }; ts[i].start(); }
		 * 
		 * for (int i = 0; i < ts.length; i++) { try { ts[i].join(); } catch
		 * (InterruptedException e) { e.printStackTrace(); } }
		 */

		StatKeeper.saveNotableStats(
				simConfig.nServers + "_" + simConfig.scenario.name() + "_"
						+ simConfig.repType.name() + "_"
						+ simConfig.graphType.name() + "_"
						+ (VideoUnit.getAvgSize() / 1000) + "_"
						+ (int) (simConfig.churnProbability * 100) + ".csv",
				notableStats, true);
	}
}
